﻿#region Librerias

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using ESF.General;
using System.Linq;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;

#endregion


namespace ESF.General
{
	public static class ImageHelper
	{
		#region Ajuste

		public static void Invert (Bitmap b)
		{
			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *) Scan0;

				int nOffset = stride - b.Width*3;
				int nWidth = b.Width * 3;
	
				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < nWidth; ++x )
					{
						p[0] = (byte)(255-p[0]);
						++p;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);
		}

		public static void GrayScale(Bitmap b)
		{
			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;

				int nOffset = stride - b.Width*3;

				byte red, green, blue;
	
				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < b.Width; ++x )
					{
						blue = p[0];
						green = p[1];
						red = p[2];

						p[0] = p[1] = p[2] = (byte)(.299 * red + .587 * green + .114 * blue);

						p += 3;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);
		}

		public static bool Brightness(Bitmap b, int nBrightness)
		{
			if (nBrightness < -255 || nBrightness > 255)
				return false;

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			int nVal = 0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;

				int nOffset = stride - b.Width*3;
				int nWidth = b.Width * 3;

				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < nWidth; ++x )
					{
						nVal = (int) (p[0] + nBrightness);
		
						if (nVal < 0) nVal = 0;
						if (nVal > 255) nVal = 255;

						p[0] = (byte)nVal;

						++p;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);

			return true;
		}

		public static bool Contrast(Bitmap b, int nContrast)
		{
			if (nContrast < -100) return false;
			if (nContrast >  100) return false;

			double pixel = 0, contrast = (100.0+nContrast)/100.0;

			contrast *= contrast;

			int red, green, blue;
			
			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;

				int nOffset = stride - b.Width*3;

				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < b.Width; ++x )
					{
						blue = p[0];
						green = p[1];
						red = p[2];
				
						pixel = red/255.0;
						pixel -= 0.5;
						pixel *= contrast;
						pixel += 0.5;
						pixel *= 255;
						if (pixel < 0) pixel = 0;
						if (pixel > 255) pixel = 255;
						p[2] = (byte) pixel;

						pixel = green/255.0;
						pixel -= 0.5;
						pixel *= contrast;
						pixel += 0.5;
						pixel *= 255;
						if (pixel < 0) pixel = 0;
						if (pixel > 255) pixel = 255;
						p[1] = (byte) pixel;

						pixel = blue/255.0;
						pixel -= 0.5;
						pixel *= contrast;
						pixel += 0.5;
						pixel *= 255;
						if (pixel < 0) pixel = 0;
						if (pixel > 255) pixel = 255;
						p[0] = (byte) pixel;					

						p += 3;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);

			return true;
		}
	
		public static bool Gamma(Bitmap b, double red, double green, double blue)
		{
			if (red < .2 || red > 5) return false;
			if (green < .2 || green > 5) return false;
			if (blue < .2 || blue > 5) return false;

			byte [] redGamma = new byte [256];
			byte [] greenGamma = new byte [256];
			byte [] blueGamma = new byte [256];

			for (int i = 0; i< 256; ++i)
			{
				redGamma[i] = (byte)Math.Min(255, (int)(( 255.0 * Math.Pow(i/255.0, 1.0/red)) + 0.5));
				greenGamma[i] = (byte)Math.Min(255, (int)(( 255.0 * Math.Pow(i/255.0, 1.0/green)) + 0.5));
				blueGamma[i] = (byte)Math.Min(255, (int)(( 255.0 * Math.Pow(i/255.0, 1.0/blue)) + 0.5));
			}

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;

				int nOffset = stride - b.Width*3;

				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < b.Width; ++x )
					{
						p[2] = redGamma[ p[2] ];
						p[1] = greenGamma[ p[1] ];
						p[0] = blueGamma[ p[0] ];

						p += 3;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);

			return true;
		}

		
		/*public static bool Color (Bitmap b, int red, int green, int blue)
		{
			if (red < -255 || red > 255) return false;
			if (green < -255 || green > 255) return false;
			if (blue < -255 || blue > 255) return false;

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;

				int nOffset = stride - b.Width*3;
				int nPixel;

				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < b.Width; ++x )
					{
						nPixel = p[2] + red;
						nPixel = Math.Max(nPixel, 0);
						p[2] = (byte)Math.Min(255, nPixel);

						nPixel = p[1] + green;
						nPixel = Math.Max(nPixel, 0);
						p[1] = (byte)Math.Min(255, nPixel);

						nPixel = p[0] + blue;
						nPixel = Math.Max(nPixel, 0);
						p[0] = (byte)Math.Min(255, nPixel);

						p += 3;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);

			return true;
		}*/

		#endregion


		#region Crop

		 public static Bitmap Crop(Bitmap bmp)
    {
        int w = bmp.Width;
        int h = bmp.Height;

        Func<int, bool> allWhiteRow = row =>
        {
            for (int i = 0; i < w; ++i)
                if (bmp.GetPixel(i, row).R != 255)
                    return false;
            return true;
        };

        Func<int, bool> allWhiteColumn = col =>
        {
            for (int i = 0; i < h; ++i)
                if (bmp.GetPixel(col, i).R != 255)
                    return false;
            return true;
        };

        int topmost = 0;
        for (int row = 0; row < h; ++row)
        {
            if (allWhiteRow(row))
                topmost = row;
            else break;
        }

        int bottommost = 0;
        for (int row = h - 1; row >= 0; --row)
        {
            if (allWhiteRow(row))
                bottommost = row;
            else break;
        }

        int leftmost = 0, rightmost = 0;
        for (int col = 0; col < w; ++col)
        {
            if (allWhiteColumn(col))
                leftmost = col;
            else
                break;
        }

        for (int col = w - 1; col >= 0; --col)
        {
            if (allWhiteColumn(col))
                rightmost = col;
            else
                break;
        }

        if (rightmost == 0) rightmost = w; // As reached left
        if (bottommost == 0) bottommost = h; // As reached top.

        int croppedWidth = rightmost - leftmost;
        int croppedHeight = bottommost - topmost;

        if (croppedWidth == 0) // No border on left or right
        {
            leftmost = 0;
            croppedWidth = w;
        }

        if (croppedHeight == 0) // No border on top or bottom
        {
            topmost = 0;
            croppedHeight = h;
        }

        try
        {
            var target = new Bitmap(croppedWidth, croppedHeight);
            using (Graphics g = Graphics.FromImage(target))
            {
                g.DrawImage(bmp,
                  new RectangleF(0, 0, croppedWidth, croppedHeight),
                  new RectangleF(leftmost, topmost, croppedWidth, croppedHeight),
                  GraphicsUnit.Pixel);
            }
            return target;
        }
        catch (Exception ex)
        {
            throw new Exception(
              string.Format("Values are topmost={0} btm={1} left={2} right={3} croppedWidth={4} croppedHeight={5}", topmost, bottommost, leftmost, rightmost, croppedWidth, croppedHeight),
              ex);
        }
    }

		#endregion


		#region Rotate

		public static Bitmap Rotate (Bitmap bmpSrc, float theta)
		{
		if (theta == 0) { return (Bitmap) bmpSrc.Clone (); }


		Matrix mRotate = new Matrix();
		mRotate.Translate(bmpSrc.Width / -2, bmpSrc.Height / -2, MatrixOrder.Append);
		mRotate.RotateAt(theta, new Point(0, 0), MatrixOrder.Append);
        
		    using (GraphicsPath gp = new GraphicsPath())
            {  // transform image points by rotation matrix
                gp.AddPolygon(new Point[] { new Point(0, 0), new Point(bmpSrc.Width, 0), new Point(0, bmpSrc.Height) });
                gp.Transform(mRotate);
                PointF[] pts = gp.PathPoints;

                // create destination bitmap sized to contain rotated source image
                Rectangle bbox = boundingBox(bmpSrc, mRotate);
                Bitmap bmpDest = new Bitmap(bbox.Width, bbox.Height);

                using (Graphics gDest = Graphics.FromImage(bmpDest))
                {  // draw source into dest
                    Matrix mDest = new Matrix();
                    mDest.Translate(bmpDest.Width / 2, bmpDest.Height / 2, MatrixOrder.Append);
                    gDest.Transform = mDest;
                    gDest.DrawImage(bmpSrc, pts);
                    gDest.DrawRectangle(Pens.Red, bbox);
                    //drawAxes(gDest, Color.Red, 0, 0, 1, 100, "");
                    return bmpDest;
                }
            }
        }

        private static Rectangle boundingBox(Image img, Matrix matrix)
        {
            GraphicsUnit gu = new GraphicsUnit();
            Rectangle rImg = Rectangle.Round(img.GetBounds(ref gu));

            // Transform the four points of the image, to get the resized bounding box.
            Point topLeft = new Point(rImg.Left, rImg.Top);
            Point topRight = new Point(rImg.Right, rImg.Top);
            Point bottomRight = new Point(rImg.Right, rImg.Bottom);
            Point bottomLeft = new Point(rImg.Left, rImg.Bottom);
            Point[] points = new Point[] { topLeft, topRight, bottomRight, bottomLeft };
            GraphicsPath gp = new GraphicsPath(points,
                                                                new byte[] { (byte)PathPointType.Start, (byte)PathPointType.Line, (byte)PathPointType.Line, (byte)PathPointType.Line });
            gp.Transform(matrix);
            return Rectangle.Round(gp.GetBounds());
        }

		#endregion


		#region B&W

		public static Bitmap ToBlackAndWhite (Image pImage)
		{
			return ToBlackAndWhite (pImage, 0.4);
		}


		public static Bitmap ToBlackAndWhite (Image pImage, double pBlackBrightness)
		{
			Bitmap imgBlackWhite;

			Color currentColor;
			

			imgBlackWhite = (Bitmap) pImage;

			for (int x = 0; x < imgBlackWhite.Width; x++)
			{
				for (int y = 0; y < imgBlackWhite.Height; y++)
				{
					currentColor = imgBlackWhite.GetPixel (x, y);

					// Si el brillo es muy bajo es un tono negro
					if (currentColor.GetBrightness () < pBlackBrightness)
					{
						imgBlackWhite.SetPixel (x, y, System.Drawing.Color.Black);
					}
					else
					{
						imgBlackWhite.SetPixel (x, y, System.Drawing.Color.White);
					}
				}
			}

			return imgBlackWhite;
		}


		//public static Bitmap ResizeImage(System.Drawing.Image image, double pPercentage)
		//{
		//    return ResizeImage (image, (int) Math.Ceiling (image.Width * pPercentage), (int) Math.Ceiling (image.Height * pPercentage));
		//}


		#region Calcs

		public static void GetTumbnailDimensions(int pOriginalWidth, int pOriginalHeight, int pLimitWidth, int pLimitHeight, out int pTumbnailWidth, out int pTumbnailHeight)
		{
			if (pOriginalHeight > pLimitHeight)
			{
				pTumbnailHeight = pLimitHeight;

				// Se aplica regla de tres para obtener la relacion entre el ancho y el alto
				pTumbnailWidth = Convert.ToInt32(pLimitHeight * pOriginalWidth / pOriginalHeight);
			}
			else if (pOriginalWidth > pLimitWidth)
			{
				pTumbnailWidth = pLimitWidth;

				// Se aplica regla de tres para obtener la relacion entre el alto y el ancho
				pTumbnailHeight = Convert.ToInt32(pLimitWidth * pOriginalHeight / pOriginalWidth);
			}
			else
			{
				pTumbnailHeight = pLimitHeight;
				pTumbnailWidth = pLimitWidth;
			}
		}

		#endregion


		public static Image GenerateTumbnail (string pFilename, int pWidth, int pHeight)
		{
			return GenerateTumbnail (pFilename, null, pWidth, pHeight);
		}


		public static Image GenerateTumbnail (string pFilename, string pTumbnailGeneratedFilename, int pWidth, int pHeight)
		{
			bool replace;
			
			Image temp;
			Image result;
			
			
			replace = (pFilename == pTumbnailGeneratedFilename);

			if (replace)
			{
				pTumbnailGeneratedFilename +="x";

				File.Move (pFilename, pTumbnailGeneratedFilename);

				using (FileStream fs = FileHelper.GetFileStream (pTumbnailGeneratedFilename))
				{				
					temp = Image.FromStream (fs);

					result =
						GenerateTumbnail 
						(
							temp,
							pFilename,
							pWidth, pHeight
						);

					temp.Dispose ();
				}

				FileHelper.Delete(pTumbnailGeneratedFilename);
			}
			else
			{
				using (FileStream fs = FileHelper.GetFileStream(pFilename))
				{
					temp = Image.FromStream (fs);

					result =
						GenerateTumbnail 
						(
							temp,
							pTumbnailGeneratedFilename,
							pWidth, pHeight
						);

					temp.Dispose();
				}
			}

			return result;
		}
		

		public static Image GenerateTumbnail (Image pImage, double pPercentage)
		{
			return GenerateTumbnail (pImage, null, (int) Math.Ceiling (pImage.Width * pPercentage), (int) Math.Ceiling (pImage.Height * pPercentage));
		}
	


		private static Image GenerateTumbnail (Image pImage, string pTumbnailGeneratedFilename, int pLimitWidth, int pLimitHeight)
		{
			int tumbHeight;
			int tumbWidth;

			Image result;

			//ImageFormat format;


			GetTumbnailDimensions(pImage.Width, pImage.Height, pLimitWidth, pLimitHeight, out tumbWidth, out tumbHeight);

			result = pImage.GetThumbnailImage(tumbWidth, tumbHeight, delegate() { return true; }, IntPtr.Zero);
			
			/*
			switch (pImage.PixelFormat)
			{
				case PixelFormat.Format1bppIndexed:
					//result = ConvertTo1bpp ((Bitmap) result);
					format = ImageFormat.Png;
				break;

				case PixelFormat.Format4bppIndexed:
				case PixelFormat.Format8bppIndexed:
				case PixelFormat.Indexed:
					format = ImageFormat.Png;
				break;

				default:
					format = ImageFormat.Jpeg;
				break;

				// TODO: Analizar y mejorar
			}*/

			
			if (!string.IsNullOrEmpty (pTumbnailGeneratedFilename))
			{
				FileHelper.Delete (pTumbnailGeneratedFilename);

				FileHelper.EnsureFolder (pTumbnailGeneratedFilename);

				result.Save (pTumbnailGeneratedFilename);
			}

			return result;
		}


		private static Bitmap ConvertTo1bpp (Bitmap img)
		{
			BitmapData bmdo = img.LockBits(new Rectangle(0, 0, img.Width, img.Height),
											ImageLockMode.ReadOnly, 
											img.PixelFormat);

			// and the new 1bpp bitmap
			Bitmap bm = new Bitmap(img.Width, img.Height, PixelFormat.Format1bppIndexed);
			BitmapData bmdn = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height),
											ImageLockMode.ReadWrite, 
											PixelFormat.Format1bppIndexed);

			// scan through the pixels Y by X
			int x, y;
			for (y = 0; y < img.Height; y++)
			{
				for (x = 0; x < img.Width; x++)
				{
					//generate the address of the colour pixel
					int index = y * bmdo.Stride + (x * 4);
					
					//check its brightness
					if ( Color.FromArgb(Marshal.ReadByte(bmdo.Scan0, index + 2),
									Marshal.ReadByte(bmdo.Scan0, index + 1),
									Marshal.ReadByte(bmdo.Scan0, index)).GetBrightness() > 0.5f)
						SetIndexedPixel(x, y, bmdn, true); //set it if its bright.
				}
			}

			// tidy up
			bm.UnlockBits(bmdn);
			img.UnlockBits(bmdo);

			return bm;
		}


		private static void SetIndexedPixel(int x, int y, BitmapData bmd, bool pixel)
		{
			int index = y * bmd.Stride + (x >> 3);
			byte p = Marshal.ReadByte(bmd.Scan0, index);
			byte mask = (byte)(0x80 >> (x & 0x7));
			if (pixel)
				p |= mask;
			else
				p &= (byte)(mask ^ 0xff);
			Marshal.WriteByte(bmd.Scan0, index, p);
		}

		#endregion


		#region Size

		public static Image Crop (Image pImage, SliceSection pSliceSection)
		{
			Rectangle slice;


			slice = new Rectangle ();

			switch (pSliceSection)
			{
				case SliceSection.Top: 
					slice.Location = new Point (0, 0);
					slice.Size = new Size (pImage.Width, pImage.Height / 2);
					break;
				
				case SliceSection.Bottom:
					slice.Location = new Point (0, pImage.Height / 2);
					slice.Size = new Size (pImage.Width, pImage.Height / 2);
					break;

				case SliceSection.TopLeft:
					slice.Location = new Point (0, 0);
					slice.Size = new Size (pImage.Width / 2, pImage.Height / 2);
					break;

				case SliceSection.TopRight:
					slice.Location = new Point (pImage.Width / 2, 0);
					slice.Size = new Size (pImage.Width / 2, pImage.Height / 2);
					break;

				case SliceSection.BottomLeft:
					slice.Location = new Point (0, pImage.Width / 2);
					slice.Size = new Size (pImage.Width / 2, pImage.Height / 2);
					break;

				case SliceSection.BottomRight:
					slice.Location = new Point (pImage.Width / 2, pImage.Width / 2);
					slice.Size = new Size (pImage.Width / 2, pImage.Height / 2);
					break;

				// Full
				default:
					slice.Location = new Point (0, 0);
					slice.Size = new Size (pImage.Width, pImage.Height);
					break;
			}

			return Crop (pImage, slice);
		}


		public static Image Crop (Image img, Rectangle cropArea)
		{
		   Bitmap bmpImage = new Bitmap(img);
		   Bitmap bmpCrop = bmpImage.Clone(cropArea,
		   bmpImage.PixelFormat);
		   return (Image)(bmpCrop);
		}

		#endregion


		#region Format

		private static bool StartsWith(this byte[] thisBytes, byte[] thatBytes)
	    {
	        for (int i = 0; i < thatBytes.Length; i += 1)
	        {
	            if (thisBytes[i] != thatBytes[i])
	            {
	                return false;
	            }
	        }
	        return true;
	    }
		
		
		private static Dictionary<byte[], ImageFormat> imageFormatDecoders = new Dictionary<byte[], ImageFormat>()
	    {
	        { new byte[]{ 0x42, 0x4D }, ImageFormat.Bmp},
	        { new byte[]{ 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, ImageFormat.Gif },
	        { new byte[]{ 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, ImageFormat.Gif },
	        { new byte[]{ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, ImageFormat.Png },
	        { new byte[]{ 0xff, 0xd8 }, ImageFormat.Jpeg },
	    };

		public static ImageFormat GetImageFormat(byte[] imageBytes)
	    {
	        MemoryStream ms = new MemoryStream(imageBytes);
	
	        
	        using (BinaryReader br = new BinaryReader(ms))
	        {
	            int maxMagicBytesLength = imageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length;
	
	            byte[] magicBytes = new byte[maxMagicBytesLength];
	
	            for (int i = 0; i < maxMagicBytesLength; i += 1)
	            {
	                magicBytes[i] = br.ReadByte();
	
	                foreach (var kvPair in imageFormatDecoders)
	                {
	                    if (magicBytes.StartsWith(kvPair.Key))
	                    {
	                        return kvPair.Value;
	                    }
	                }
	            }
	        }

			return null;
	    }

		#endregion


		#region Compress

		public static void Compress (string pFilename, string pFilenameResult, int pLevel)
		{
			Bitmap bmp;
			
			ImageCodecInfo jgpEncoder;

			
			if (pFilename == pFilenameResult)
			{
				FileHelper.Delete (pFilename + "x");
				File.Copy (pFilename, pFilename + "x", true);

				pFilename += "x";
			}

			bmp = new Bitmap (pFilename);
			
			jgpEncoder = GetEncoder (bmp.RawFormat);

			System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;

			EncoderParameters myEncoderParameters = new EncoderParameters(1);

			EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, pLevel);
			myEncoderParameters.Param[0] = myEncoderParameter;
			
			FileHelper.Delete (pFilenameResult);

			bmp.Save (pFilenameResult, jgpEncoder, myEncoderParameters);

			bmp.Dispose ();
			
			FileHelper.Delete (pFilename);
		}


		public static ImageCodecInfo GetEncoder(ImageFormat format)
		{
			ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();


			foreach (ImageCodecInfo codec in codecs)
			{
				if (codec.FormatID == format.Guid)
				{
					return codec;
				}
			}
			
			return null;
		}

		#endregion
	}
}
